大家好,今天是鐵人賽第十天。今天我要來介紹go語言的映射,它和切片一樣是抽象的容器型別,底層也是用陣列來實作。映射與陣列切片最大差別在於,映射可以使用非整數索引型別及不連續的索引值,這是因為映射的底層是用雜湊表(Hash table)實作。
映射(map)在一些語言裡稱為字典(dictionary),指的是以鍵-值(key-value)存取的資料容器。映射也是一種型別,我們可以直接宣告它,初始值為nil:
// 宣告一個字串為鍵值的映射
var emptyMap map[string]
fmt.Printf("len=%d, value=%v \n", len(emptyMap), emptyMap)
// 宣告一個字串為鍵及整數為值的映射,並指定初始值
var ageOfHeros = map[string]int{"IronMan": 30, "Dr.Strange": 45}
fmt.Printf("len=%d, value=%v \n", len(ageOfHeros), ageOfHeros)
執行結果:
len=0, value=map[]
len=2, value=map[Dr.Strange:45 IronMan:30]
映射的存取和陣列相似,可以透過 [ ]
來存取資料,如下:
var ageOfHeros = map[string]int{"IronMan": 30, "Dr.Strange": 45}
age1 := ageOfHeros["IronMan"]
fmt.Println("IronMan:", age1)
age2 := ageOfHeros["Thor"]
fmt.Println("Thor:", age2)
ageOfHeros["Hulk"] = 52
age3 := ageOfHeros["Hulk"]
fmt.Println("Hulk:", age3)
fmt.Printf("len=%d, value=%v \n", len(ageOfHeros), ageOfHeros)
執行結果:
IronMan: 30
Thor: 0
Hulk: 52
len=3, value=map[Dr.Strange:45 Hulk:52 IronMan:30]
從上面結果來看,我們可以發現在映射中沒有Thor這筆資料,取出來的是型別預設值 0。
另外,我們也可以透過 for
印出映射的內容:
// 招喚四個漫威英雄
var ageOfHeros = map[string]int{"IronMan": 30, "Dr.Strange": 45, "Hulk": 52, "Thor": 66}
for k, v := range ageOfHeros {
fmt.Println(k, v)
}
執行結果:
Thor 66
IronMan 30
Dr.Strange 45
Hulk 52
這邊要注意的是,映射巡覽的順序不一定會和初始值或資料插入的順序一致,因為它的索引值是不連續的。
一般來說,我們在用一個 key 讀取映射時,會先檢查這個 key 是否存在,然後再取得 value。在go語言也有提供這樣的作法:
var ageOfHeros = map[string]int{"IronMan": 30}
// 第二個回傳值為 bool 型別,表示是否存在
age, ok := ageOfHeros["Dr.Strange"]
if ok {
fmt.Println("Dr.Strange:", age)
}
即使我們已經知道對映射讀取不存在的 key 會回傳預設值,但如果用預設值來判斷資料是否存在,這樣的作法並不適合,真實的資料很有可能和預設值相同。
由於映射與切片一樣是go語言內建的抽象容器,所以我們也可以用 make()
來動態建立一個新的映射:
var ageOfHeros = make(map[string]int)
ageOfHeros["Dr.Strange"] = 45
age, ok := ageOfHeros["Dr.Strange"]
if ok {
fmt.Println("Dr.Strange:", age)
}
// 印出: Dr.Strange: 45
由於映射裡的資料是分散的,無法像陣列可以用元素覆蓋的方式刪除資料。因此,go語言提供了delete()
函式,用來刪除已存在的鍵值:
var ageOfHeros = map[string]int{"IronMan": 30, "Dr.Strange": 45}
// 刪除存在的key
delete(ageOfHeros, "IronMan")
delete(ageOfHeros, "Dr.Strange")
// 刪除不存在的key不會造成錯誤
delete(ageOfHeros, "Thor")
fmt.Printf("len=%d, value=%v \n", len(ageOfHeros), ageOfHeros)
// 印出: len=0, value=map[]
我們在day5有提過go語言的函式也是一種型別,可以當作變數使用。因此,我們也可以把函式放到映射裡,感覺很像是Javascript裡的物件(object),只是在go語言必須定義型別。下面舉一個加減乘除的範例:
// 建立一個接收兩個整數及回傳一個字串的函式映射容器
var operation = make(map[string]func(a, b int) int)
// 新增加法
operation["add"] = func(a, b int) int {
return a + b
}
// 新增減法
operation["minus"] = func(a, b int) int {
return a - b
}
// 新增乘法
operation["multiply"] = func(a, b int) int {
return a * b
}
// 新增除法
operation["divide"] = func(a, b int) int {
return a / b
}
// 執行所有映射元素,
for k, v := range operation {
fmt.Printf("%s(1,2) = %d \n", k, v(1, 2))
}
執行結果:
add(1,2) = 3
minus(1,2) = -1
multiply(1,2) = 2
divide(1,2) = 0
今天介紹了go語言的映射用法,這種資料結構在很多語言都有被實作,像是C#的Dictionary。而使用映射的目的,除了能表達複雜形式的資料(ex: JSON)外,也可以提高查詢效能,像是快取資料通常也是基於映射的資料結構。
倒數第二張圖的
// 建立一個接收兩個整數及回傳一個 字串 的函式映射容器
是不是寫錯了XD
// 建立一個接收兩個整數及回傳一個 整數 且 鍵值為字串
的函式映射容器